#include <stdlib.h>
#include <stddef.h> /// for offsetof
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>

#include "rsc.h"
#include "avi.h"

/******* offset calculate ***********/
#define RIFF_SIZE_OFFSET offsetof(struct avi_file,file_size)

#define STRH_LIST_OFFSET \
			(offsetof(struct avi_file,header)+ \
			 offsetof(struct header_list,video_str))

#define MOVI_SIZE_OFFEST(adjust) \
			(offsetof(struct avi_file,junk)+(adjust)+ \
			 offsetof(struct movie_list, list_size))

#define MAIN_HEADER_OFFSEET \
			(offsetof(struct avi_file,header)+ \
			 offsetof(struct header_list,header)+ \
			 offsetof(struct avi_header_list, main_header))
#define TOTAL_FRAMES_OFFSET \
			 (MAIN_HEADER_OFFSEET + \
			  offsetof(struct main_avi_header, totalframe))

#define STREAM_NUMBER_OFFSET \
			(MAIN_HEADER_OFFSEET + \
			 offsetof(struct main_avi_header, streams))

#define VIDEO_FRAMES_OFFSET \
			(STRH_LIST_OFFSET + \
			 offsetof(struct video_stream_header_list, str_header)+ \
			 offsetof(struct stream_header, length))

#define FIRST_STREAM_TYPE_OFFSET \
			(STRH_LIST_OFFSET + \
			 offsetof(struct video_stream_header_list, str_header)+ \
			 offsetof(struct stream_header, fcc_type))

static AVI_HANDLE avi_prepare_env(const char *filename, struct avi_media_param *mparam,
		int strtype)
{
	if(filename==NULL ||mparam==NULL)
	//	|| strncmp(strrchr(filename,'.'),".avi",4))
	{
		printf("invalid avi file name:%s,or media param is NULL!\n",filename);
		return NULL;
	}
	if(strlen(filename)>=64)
	{
		printf("avi filename[%s] is greater than 64.\n",filename);
		return NULL;
	}
	if(strtype < AVI_STREAM_VIDEO || strtype >= AVI_STREAM_MAX)
	{
		printf("stream type[%d] is invalid!\n",strtype);
		return NULL;
	}
	AVI_HANDLE pavi = NULL;
	pavi = calloc(1,sizeof(struct avi_handle));
	if(pavi == NULL)
	{
		printf("calloc memory fail!\n");
		return NULL;
	}
	/// open file to write 
	FILE *file = fopen(filename,"wb");
	if(file == NULL)
	{
		printf("fopen file %s err:%s\n",filename,strerror(errno));
		free(pavi);
		return NULL;
	}
	strcpy(pavi->fname,filename);
	pavi->fp = file;
	pavi->riff_size_offset = RIFF_SIZE_OFFSET;
	return pavi;
}

static int fill_main_header(struct avi_header_list *avih,
		int strnum, int frate,int brate,int w, int h)
{
	avih->avih = mkFOURCC('a','v','i','h');
	avih->avih_size = sizeof(struct avi_header_list)-8;

	avih->main_header.microsec_perframe = 1000000 * 1/frate; 
	avih->main_header.maxbytes_persec = brate/8; 
	avih->main_header.paddinggranularity = 0; // 2048
	avih->main_header.flags = AVIF_ISINTERLEAVED|AVIF_TRUSTCKTYPE; 
	avih->main_header.totalframe = 0; ///should update when finish avi file
	avih->main_header.initialframes = 0;
	avih->main_header.streams = strnum;
	avih->main_header.suggested_bufsize = 1024*1024;/// use 0 if not sure
	avih->main_header.width = w;
	avih->main_header.height = h;
	memset(avih->main_header.reserved,0,sizeof(avih->main_header.reserved));

	return 0;
}
static int fill_video_stream_header(struct video_stream_header_list *vstr,
									int frate, int w, int h)
{
	vstr->list = mkFOURCC('L','I','S','T');
	vstr->list_size = sizeof(struct video_stream_header_list)-8;
	vstr->list_type = mkFOURCC('s','t','r','l');

	/// stream header 
	vstr->str_header.block = mkFOURCC('s','t','r','h');
	vstr->str_header.list_size = sizeof(struct stream_header)-8;
	vstr->str_header.fcc_type = mkFOURCC('v','i','d','s');
	vstr->str_header.fcc_handler = mkFOURCC('H','2','6','4');/// FIXME
	vstr->str_header.flags = 0;
	vstr->str_header.priority = 0;
	vstr->str_header.language = 0;
	vstr->str_header.initialframes= 0;
	vstr->str_header.scale = 1;
	vstr->str_header.rate = frate;/// framerate
	printf("video frate = %d\n",frate);
	vstr->str_header.start = 0;
	vstr->str_header.length = 0;/// video total frames,need update
	vstr->str_header.suggested_bufsize = 1024*1024;/// use 0 if not sure
	vstr->str_header.quality = 0xffffffff;/// -1 to use default
	vstr->str_header.samplesize = 0;
	vstr->str_header.frame_rect.left = 0;
	vstr->str_header.frame_rect.top = 0;
	vstr->str_header.frame_rect.right = w;/// width
	vstr->str_header.frame_rect.bottom= h;/// hieght 

	/// stream format
	vstr->str_format.block = mkFOURCC('s','t','r','f');
	vstr->str_format.block_size = sizeof(struct bitmapinfo); 
	vstr->str_format.bmp_info.size = sizeof(struct bitmapinfo);
	vstr->str_format.bmp_info.width = w; 
	vstr->str_format.bmp_info.height = h;
	vstr->str_format.bmp_info.planes = 1;
	vstr->str_format.bmp_info.bit_count = 24;
	vstr->str_format.bmp_info.compression = mkFOURCC('H','2','6','4');
	vstr->str_format.bmp_info.image_size = 
		(vstr->str_format.bmp_info.width*
		 vstr->str_format.bmp_info.height*vstr->str_format.bmp_info.bit_count)/8;
	vstr->str_format.bmp_info.x_pixels = 0;
	vstr->str_format.bmp_info.y_pixels = 0;
	vstr->str_format.bmp_info.clrused = 0;
	vstr->str_format.bmp_info.clr_important = 0;

	return 0;
}
static int fill_audio_stream_header(struct audio_stream_header_list *astr,
									int chns, int bits, int sample_rate)
{
	astr->list = mkFOURCC('L','I','S','T');
	astr->list_size = sizeof(struct audio_stream_header_list)-8;
	astr->list_type = mkFOURCC('s','t','r','l');

	/// stream header 
	astr->str_header.block = mkFOURCC('s','t','r','h');
	astr->str_header.list_size = sizeof(struct stream_header)-8;
	astr->str_header.fcc_type = mkFOURCC('a','u','d','s');
	astr->str_header.fcc_handler = mkFOURCC('G','7','1','1');///FIXME,0x01
	astr->str_header.flags = 0;
	astr->str_header.priority = 0;
	astr->str_header.language = 0;
	astr->str_header.initialframes = 0;
	astr->str_header.scale = 1;
	astr->str_header.rate = sample_rate;
	astr->str_header.start = 0;
	astr->str_header.length = 320;/// frame size
	astr->str_header.suggested_bufsize = 12288; /// use 0 if not sure
	astr->str_header.quality = 0xffffffff;/// -1 to use default
	astr->str_header.samplesize = (chns*bits)/8;/// = block_align 
	astr->str_header.frame_rect.left = 0;
	astr->str_header.frame_rect.top = 0;
	astr->str_header.frame_rect.right = 0; 
	astr->str_header.frame_rect.bottom= 0; 

	/// stream format
	astr->str_format.block = mkFOURCC('s','t','r','f');
	astr->str_format.block_size = sizeof(struct audio_stream_format)-8; 
	astr->str_format.format_tag = 0x06;//G711 a-law: 0x06,u-law:0x07; WAVE_FORMAT_PCM;
	astr->str_format.channels = chns;
	astr->str_format.samples_persec = sample_rate;
	astr->str_format.bits_persample = bits;
	astr->str_format.block_align = (chns*bits)/8; 
	astr->str_format.avgbytes_persec = sample_rate*astr->str_format.block_align;

	return 0;
}

static int avi_write_header(struct avi_handle *pavi, struct avi_media_param *mparam,int strtype)
{
	struct avi_file *avif = &pavi->avi_ctx;
	int strnum = 2; // default is 2 stream(video & audio)
	int hdrl_size = sizeof(struct header_list)-8;// not have junk
	int hdrl_adjust = 0;
	int width = mparam->video_width, height = mparam->video_height; 

	//memset(avif,0,sizeof(struct avi_file));
	if(strtype != AVI_STREAM_VAMIX)
		strnum = 1;
	if(strtype == AVI_STREAM_VIDEO)
		hdrl_adjust -= sizeof(struct audio_stream_header_list);
	else if(strtype == AVI_STREAM_AUDIO)
	{
		//hdrl_adjust -= sizeof(struct video_stream_header_list);
		width = 0;
		height = 0;
	}
	
	avif->riff = mkFOURCC('R','I','F','F');
	avif->file_size = MOVI_SIZE_OFFEST(hdrl_adjust)+0x20000;// initial size
	avif->file_type = mkFOURCC('A','V','I',' ');
	
	// header list
	avif->header.list = mkFOURCC('L','I','S','T');
	avif->header.list_size = hdrl_size+hdrl_adjust; 
	avif->header.list_type = mkFOURCC('h','d','r','l');

	/// need to update total frames when finish avi file.
	fill_main_header(&avif->header.header,strnum,mparam->video_framerate,
			mparam->video_bitrate,mparam->video_width,mparam->video_height);
	pavi->total_frames_offset = TOTAL_FRAMES_OFFSET;

	/// need to update video total frames when finish avi file.
//	if(strtype != AVI_STREAM_AUDIO)
	{
		fill_video_stream_header(&avif->header.video_str,
				mparam->video_framerate, width,height);
		pavi->video_frames_offset = VIDEO_FRAMES_OFFSET;
	}

	if(strtype != AVI_STREAM_VIDEO)
		fill_audio_stream_header(&avif->header.audio_str,mparam->audio_channels,
				mparam->audio_sample_fmt,mparam->audio_sample_rate);

	// movie list header
	avif->movie.list = mkFOURCC('L','I','S','T');
	avif->movie.list_size = 0x20000; /// init 'movi' size to 128KB 
	avif->movie.movi = mkFOURCC('m','o','v','i');

	// write header to file
	int ret = 0;
	ret = fwrite((void*)avif,STRH_LIST_OFFSET,1,pavi->fp);
	if(ret != 1)
	{
		printf("fwrite avi header err:%s\n",strerror(errno));
		return -1;
	}
	// write stream header and format list
//	if(strtype != AVI_STREAM_AUDIO)
	{
		ret = fwrite((void*)&avif->header.video_str,sizeof(struct video_stream_header_list),1,pavi->fp);
		if(ret != 1)
		{
			printf("fwrite avi video stream header err:%s\n",strerror(errno));
			return -1;
		}
	}
	if(strtype != AVI_STREAM_VIDEO)
	{
		ret = fwrite((void*)&avif->header.audio_str,sizeof(struct audio_stream_header_list),1,pavi->fp);
		if(ret != 1)
		{
			printf("fwrite avi audio stream header err:%s\n",strerror(errno));
			return -1;
		}
	}

	/***************************************************/
	///// may to wrtie junk chunk to file(optional) /////
	/***************************************************/

	// write movie list info to file
	ret = fwrite((void*)&avif->movie,offsetof(struct movie_list, m_data),1,pavi->fp);
	if(ret != 1)
	{
		printf("fwrite avi movie list err:%s\n",strerror(errno));
		return -1;
	}
	/// we don't write junk chunk data to align,so get movie_size_offset below
	pavi->movie_size_offset = MOVI_SIZE_OFFEST(hdrl_adjust);
	return 0;
}

/**
 * @param[in] type stream type,0 - video stream, 1 - audio stream 
 */
int avi_write_packet(AVI_HANDLE pavi, int type, unsigned char *buf,int size)
{
	if(pavi==NULL || buf==NULL || size <= 0)
	{
		printf("invalid parameters!\n");
		return -1;
	}
	if(type != 0 && type != 1)
	{
		printf("stream type is invalid,only 0(video) and 1(audio) ok.\n");
		return -1;
	}

	//if(type == 1)
	//	printf("write audio stream packet,size = %d\n",size);
	int ret = 0, nsize = size;
	if(size%2) nsize = size+1; /// aligned WORD
	struct media_data *packet = 
		(struct media_data*)malloc(sizeof(struct media_data)+nsize*sizeof(char));
	if(packet==NULL)
	{
		printf("malloc media packet error:%s\n",strerror(errno));
		return -1;
	}
	if(type==0) // video stream
		packet->flag = mkFOURCC('0','0','d','c');
	else // audio stream
		packet->flag = mkFOURCC('0','1','w','b');

	packet->size = size;/// actual data size
	memcpy(packet->data,buf,size);
	if(nsize > size) packet->data[size] = 0;

	/// write media data to avi file
	ret = fwrite((void*)packet,8+nsize,1,pavi->fp);	
	if(ret != 1)
	{
		printf("fwrite avi packet err:%s\n",strerror(errno));
		ret = RSC_EWRIT;
	}
	else
	{ /// update avi information
		if(type==0) pavi->video_frames++;	
		else 		pavi->audio_frames++;
		pavi->mdata_size += 8+nsize;/// avi file data size
	}
	free(packet);
	packet = NULL;

	if(ret < 0) return ret;
	return 0;
}

static int avi_write_trailer(AVI_HANDLE pavi)
{
	//int ret = 0;
	//****** build and write avi OLD index, LIST idx1 ****/
	//TODO ..... optional,so now don't accomplish it
	//
	
	fflush(pavi->fp);

	/// update avi file size,total frames .etc info.
	unsigned long riff_size = 0;
	unsigned long movie_size = 0;
	unsigned long total_frames = 0;
	
	movie_size = 4+pavi->mdata_size;
	/*** we don't think of junk chunk data and OLE index question ***/
	riff_size = 4+LIST_SIZE(pavi->avi_ctx.header) + movie_size;
	total_frames = pavi->video_frames + pavi->audio_frames;
	//printf("total frames = %lu,video frames = %lu, audio frames = %lu\n",
	//		 total_frames,pavi->video_frames,pavi->audio_frames);
	pavi->avi_ctx.file_size = riff_size;
	pavi->avi_ctx.movie.list_size = movie_size;

	/* update RIFF_SIZE */
	fseek(pavi->fp,pavi->riff_size_offset+pavi->file_extra_offset,SEEK_SET);
	fwrite((void*)&riff_size,sizeof(riff_size),1,pavi->fp);

	/* update total_frames */
	fseek(pavi->fp,pavi->total_frames_offset+pavi->file_extra_offset,SEEK_SET);
	fwrite((void*)&total_frames,sizeof(total_frames),1,pavi->fp);

	/* update video_frames */
	if(pavi->video_frames_offset==VIDEO_FRAMES_OFFSET)
	{
		fseek(pavi->fp,pavi->video_frames_offset+pavi->file_extra_offset,SEEK_SET);
		fwrite((void*)&pavi->video_frames,sizeof(long),1,pavi->fp);
	}

	/* update MOVI_SIZE */
	fseek(pavi->fp,pavi->movie_size_offset+pavi->file_extra_offset,SEEK_SET);
	fwrite((void*)&movie_size,sizeof(movie_size),1,pavi->fp);
	
	fflush(pavi->fp);
	fdatasync(fileno(pavi->fp));
	fclose(pavi->fp);

	return 0;
}

/**
 * just need to fix RIFF size and MOVI size
 */
int avi_fix_handle(const char *filename)
{
	if(filename==NULL)
	{
		printf("filename is NULL!\n");
		return  -1;
	}

	FILE *fp = fopen(filename,"rb+");
	unsigned long file_size = 0;

	if(fp == NULL)
	{
		printf("fopen [%s] err:%s\n",filename,strerror(errno));
		return -1;
	}
	/***** ensure this is a AVI RIFF file ****/
	char riff[5] = {0};
	if(fread(riff,1,4,fp)!=4)
	{
		printf("fread riff header fail!\n");
		fclose(fp);
		return -1;
	}
	else if(strncmp(riff,"RIFF",4))
	{
		printf("header is [%s],not \'RIFF\'!\n",riff);
		fclose(fp);
		return -1;
	}

	fseek(fp, 0, SEEK_END);
	file_size = ftell(fp);// get file length
	fseek(fp, 0, SEEK_SET);// back to file beginning for fix

	/**** get stream number and type ****/
	int strnum = 2, adjust = 0;
	fseek(fp,STREAM_NUMBER_OFFSET,SEEK_SET);
	fread(&strnum,1,1,fp);
	printf("strnum = %d\n",strnum);
	if(strnum != 2)
	{
		char type[5] = {0};
		fseek(fp,FIRST_STREAM_TYPE_OFFSET,SEEK_SET);
		fread(type,1,4,fp);
		if(!strncmp(type,"vids",4))
			adjust -= sizeof(struct audio_stream_header_list);
//		else if(!strncmp(type,"auds",4))
//			adjust -= sizeof(struct video_stream_header_list);
	}
	unsigned long movsize_offset = MOVI_SIZE_OFFEST(adjust);
	/**** just need to update riff size and movie size ****/
	unsigned long riff_size = file_size - RIFF_SIZE_OFFSET - sizeof(riff_size);
	unsigned long movie_size= file_size - movsize_offset - sizeof(movie_size);

	/* update RIFF_SIZE */
	fseek(fp,RIFF_SIZE_OFFSET,SEEK_SET);
	fwrite((void*)&riff_size,sizeof(riff_size),1,fp);

	/* update MOVI_SIZE */
	fseek(fp,movsize_offset,SEEK_SET);
	fwrite((void*)&movie_size,sizeof(movie_size),1,fp);
	
	fflush(fp);
	fdatasync(fileno(fp));
	fclose(fp);

	return 0;
}

AVI_HANDLE avi_init_handle(const char *filename, struct avi_media_param *mparam,int strtype)
{
	int ret = 0;

	AVI_HANDLE pavi = avi_prepare_env(filename,mparam,strtype);
	if(pavi == NULL)
	{
		printf("avi prepare handle env fail!\n");
		return NULL;
	}

	ret = avi_write_header(pavi,mparam,strtype);

	if(ret < 0)
	{
		printf("avi_write_header error:%d\n",ret);
		fclose(pavi->fp);
		free(pavi);
		return NULL;
	}

	return pavi;
}

void avi_exit_handle(AVI_HANDLE pavi)
{
	if(pavi!=NULL)
	{
		avi_write_trailer(pavi);
		free(pavi);
		pavi = NULL;
	}
}

AVI_HANDLE avi_init_handle_2(const char *filename, struct avi_media_param *mparam,
		int strtype,void *data,int len)
{
	int ret = 0;

	AVI_HANDLE pavi = avi_prepare_env(filename,mparam,strtype);
	if(pavi == NULL)
	{
		printf("avi prepare handle env fail!\n");
		return NULL;
	}

	/*** write user data to file header */
	if(data != NULL && len > 0)
	{
		ret = fwrite(data,len,1,pavi->fp);
		pavi->file_extra_offset = len;
	}

	ret = avi_write_header(pavi,mparam,strtype);

	if(ret < 0)
	{
		printf("avi_write_header error:%d\n",ret);
		fclose(pavi->fp);
		free(pavi);
		return NULL;
	}

	return pavi;
}
